﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ALE_Server.ECException;
using EpcTagLibrary;
using EpcTagLibrary.Pat;
using ALE_Server.EC;
using ALE_Server.Plugin;

namespace ALE_Server
{
    public class Spec
    {
        private IList<string> m_Subscribers = new List<string>();
        private IDictionary<string,EventReport> m_PreviousReports = new Dictionary<string,EventReport>();

        private EventCycle m_CurrentEvtCycle;
        private SpecState m_State = new SpecState();
        private ECSpec m_ECSpec;
        private string m_Name;

        private long m_RepeatPeriod = 0;
        private bool m_IsRunning = true;

        public Spec(string specName, ECSpec spec)
        {
            m_ECSpec = ECSpec;
            m_Name = specName;

            if ((m_ECSpec.Boundaries.RepeatPeriod != null) && (m_ECSpec.Boundaries.RepeatPeriod.Time >0))
            {
                m_RepeatPeriod = m_ECSpec.Boundaries.RepeatPeriod.Time;
            }

            m_CurrentEvtCycle = new EventCycle(this);

            Validate(this);
        }

        public void TagEvent(TagEvent te)
        {
            if (m_IsRunning)
            {
                m_CurrentEvtCycle.TagEvent(te);
            }
        }

        public void AddSubscriber(string notificationUri)
        {
            m_Subscribers.Add(notificationUri);
            Request();
        }

        public void RemoveSubscribers(string notificationUri)
        {
            m_Subscribers.Remove(notificationUri);
        }

        public bool IsRunning
        {
            get
            {
                return m_IsRunning;
            }
            set
            {
                m_IsRunning = value;
            }
        }

        public ECSpec ECSpec
        {
            get
            {
                return m_ECSpec;
            }
        }

        public SpecState State
        {
            get
            {
                return m_State;
            }
            set
            {
                m_State = value;
            }
        }

        public string Name
        {
            get
            {
                return m_Name;
            }
        }

        public EventCycle CurrentEventCycle
        {
            get
            {
                return m_CurrentEvtCycle;
            }
        }

        public string[] Subscribers
        {
            get
            {
                return (string[])m_Subscribers.ToArray<string>();
            }
        }

        public void SetPreviousEventReport(string reportName, EventReport report)
        {
            m_PreviousReports.Add(reportName, report);
        }

        public EventReport GetPreviousReport(string reportName)
        {
            return m_PreviousReports[reportName];
        }

        private void Activate()
        {
            if (IsRunning && (m_CurrentEvtCycle != null) && (!m_CurrentEvtCycle.IsEventCycleTerminated))
            {
                m_State.State = SpecState.States.ACTIVE;
            }
        }

        private void Request()
        {
            if ((ECSpec.Boundaries.StartTrigger == null) || (ECSpec.Boundaries.StartTrigger.Length == 0))
            {
                Activate();
            }
            else
            {
                m_State.State = SpecState.States.REQUESTED;
            }
        }

        private void UnRequest()
        {
            if (m_Subscribers.Count == 0)
            {
                m_State.State = SpecState.States.UNREQUESTED;
            }
        }

        public void Subscribe(string notificationUrl)
        {
            if (m_Subscribers.Contains(notificationUrl))
            {
                throw new DuplicateNameException("The specificed ECSpec and Subscriber URL is identical to a previous one");
            }

            m_Subscribers.Add(notificationUrl);

            Request();
        }

        public void UnSubscribe(string notificationUrl)
        {
            if (!m_Subscribers.Contains(notificationUrl))
            {
                throw new NoSuchNameException("The specified ECSpec and Subscriber URL do not Exist");
            }

            m_Subscribers.Remove(notificationUrl);
            UnRequest();
        }

        public ECReports Poll()
        {
            Request();

            ECReports Result = m_CurrentEvtCycle.BuildReports();

            UnRequest();

            return Result;
        }

        public void Run()
        {
            m_CurrentEvtCycle.Tags.Clear();
            m_IsRunning = true;            
        }

        public void Stop()
        {
            m_IsRunning = false;
            m_CurrentEvtCycle.ShutDown();
        }

        public void NotifySubscribers(ECReports report)
        {
            if ((report.Reports != null) && (report.Reports.Count > 0))
            {
                foreach (string s in m_Subscribers)
                {
                    try
                    {
                        Notifier N = NotifierFactory.NewInstance(s);

                        if (N != null)
                        {
                            N.NotifySubscriber(report);
                        }
                    }
                    catch (Exception)
                    {
                    }
                }
            }
        }

        public static void Validate(Spec spec)
        {
            try
            {
                ECSpec EcSpec = spec.ECSpec;

                if ((EcSpec.Readers == null) || (EcSpec.Readers.Count == 0))
                {
                    throw new ECSpecValidationException("No LogicalReaders defined");
                }
               
                if (EcSpec.Boundaries == null)
                {
                    throw new ECSpecValidationException("No ECBoundarySpec defined");
                }

                if (EcSpec.ReportSpecs == null)
                {
                    throw new ECSpecValidationException("No ReportSpec defined");
                }

                ECBoundarySpec ECBS = EcSpec.Boundaries;

                if ((ECBS.Duration != null) && (ECBS.Duration.Time < 0) || (ECBS.RepeatPeriod != null) && (ECBS.RepeatPeriod.Time < 0) ||
                    (ECBS.StableInterval != null) && (ECBS.StableInterval.Time < 0))
                {
                    throw new ECSpecValidationException("Duration, RepeatPeriod and SetInterval cannot be negative");
                }

                if ((ECBS.StartTrigger != null) && (ECBS.StartTrigger.Trim().Length > 0) && (ECBS.RepeatPeriod != null) && (ECBS.RepeatPeriod.Time > 0))
                {
                    throw new ECSpecValidationException("StartTrigger and RepeatPeriod are mutually exclusive");
                }

                if (((ECBS.StopTrigger == null) || (ECBS.StopTrigger.Trim().Length > 0)) && ((ECBS.Duration == null) || (ECBS.Duration.Time < 1)) &&
                    ((ECBS.StableInterval == null) || (ECBS.StableInterval.Time < 1)))
                {
                    throw new ECSpecValidationException("No stopping condition specified. ");
                }

                IList<string> ReportNames = new List<string>();

                foreach (ECReportSpec rep in EcSpec.ReportSpecs)
                {
                    if (ReportNames.Contains(rep.ReportName))
                    {
                        throw new ECSpecValidationException("Duplicate ECReportSpecs names defined");
                    }
                    else 
                    {
                        ReportNames.Add(rep.ReportName);
                    }

                    if ((rep.Filter != null) && (rep.Filter.ExcludePattern != null))
                    {
                        foreach (ECExcludePattern pat in rep.Filter.ExcludePattern)
                        {
                            try
                            {
                                PatternFactory.NewInstance(pat.Value);
                            }
                            catch (Exception e)
                            {
                                throw new ECSpecValidationException("Invalid Filter Pattern",e);
                            }
                        }
                    }

                    if ((rep.Filter != null) && (rep.Filter.IncludePattern!= null))
                    {
                        foreach (ECIncludePattern pat in rep.Filter.IncludePattern)
                        {
                            try
                            {
                                PatternFactory.NewInstance(pat.Value);
                            }
                            catch (Exception e)
                            {
                                throw new ECSpecValidationException("Invalid Filter Pattern",e);
                            }
                        }
                    }

                    if ((rep.Group != null)  && (rep.Group.PatternList != null))
                    {
                        foreach (string s in rep.Group.PatternList)
                        {
                            try
                            {
                                PatternFactory.NewInstance(s);
                            }
                            catch (Exception e)
                            {
                                throw new ECSpecValidationException("Invalid Filter Pattern",e);
                            }
                        }
                    }

                    ECReportOutputSpec OutputSpec = rep.Output;

                    if (!OutputSpec.IsIncludeCount && !OutputSpec.IsIncldeEPC && !OutputSpec.IsIncludeTag && !OutputSpec.IsIncludeRawDecimal && !OutputSpec.IsIncludeRawHex)
                    {
                         throw new ECSpecValidationException("Invalid ECReportOutputSpec");
                    }
                }
            }
            catch (Exception)
            {
                throw new ECSpecValidationException("Unknown error occurred validating spec.");
            }
        }
    }
}
